package com.fib.core.common.util;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.Map;

public class ClassUtils {
	public static final String CLASS_EXTENSION = ".class";

	public static final String JAVA_EXTENSION = ".java";
	private static final int JIT_LIMIT = 5 * 1024;

	private ClassUtils() {
	}

	public static String getSimpleClassName(String qualifiedName) {
		if (null == qualifiedName) {
			return null;
		}

		int i = qualifiedName.lastIndexOf('.');
		return i < 0 ? qualifiedName : qualifiedName.substring(i + 1);
	}

	public static ClassLoader getCallerClassLoader(Class<?> caller) {
		return caller.getClassLoader();
	}

	public static Object newInstance(String name) {
		try {
			return forName(name).getDeclaredConstructor().newInstance();
		} catch (InstantiationException e) {
			throw new IllegalStateException(e.getMessage(), e);
		} catch (IllegalAccessException e) {
			throw new IllegalStateException(e.getMessage(), e);
		} catch (IllegalArgumentException e) {
			throw new IllegalStateException(e.getMessage(), e);
		} catch (InvocationTargetException e) {
			throw new IllegalStateException(e.getMessage(), e);
		} catch (NoSuchMethodException e) {
			throw new IllegalStateException(e.getMessage(), e);
		} catch (SecurityException e) {
			throw new IllegalStateException(e.getMessage(), e);
		}
	}

	public static ClassLoader getClassLoader(Class<?> clazz) {
		ClassLoader cl = null;
		try {
			cl = Thread.currentThread().getContextClassLoader();
		} catch (Throwable ex) {
			// Cannot access thread context ClassLoader - falling back to system class
			// loader...
		}
		if (cl == null) {
			// No thread context class loader -> use class loader of this class.
			cl = clazz.getClassLoader();
			if (cl == null) {
				// getClassLoader() returning null indicates the bootstrap ClassLoader
				try {
					cl = ClassLoader.getSystemClassLoader();
				} catch (Throwable ex) {
					// Cannot access system ClassLoader - oh well, maybe the caller can live with
					// null...
				}
			}
		}

		return cl;
	}

	/**
	 * Return the default ClassLoader to use: typically the thread context
	 * ClassLoader, if available; the ClassLoader that loaded the ClassUtils class
	 * will be used as fallback.
	 * <p>
	 * Call this method if you intend to use the thread context ClassLoader in a
	 * scenario where you absolutely need a non-null ClassLoader reference: for
	 * example, for class path resource loading (but not necessarily for
	 * <code>Class.forName</code>, which accepts a <code>null</code> ClassLoader
	 * reference as well).
	 *
	 * @return the default ClassLoader (never <code>null</code>)
	 * @see java.lang.Thread#getContextClassLoader()
	 */
	public static ClassLoader getClassLoader() {
		return getClassLoader(ClassUtils.class);
	}

	public static Class<?> forName(String[] packages, String className) {
		try {
			return _forName(className);
		} catch (ClassNotFoundException e) {
			if (packages != null && packages.length > 0) {
				for (String pkg : packages) {
					try {
						return _forName(pkg + "." + className);
					} catch (ClassNotFoundException e2) {
					}
				}
			}
			throw new IllegalStateException(e.getMessage(), e);
		}
	}

	public static Class<?> forName(String className) {
		try {
			return _forName(className);
		} catch (ClassNotFoundException e) {
			throw new IllegalStateException(e.getMessage(), e);
		}
	}

	public static Class<?> _forName(String className) throws ClassNotFoundException {
		if ("boolean".equals(className))
			return boolean.class;
		if ("byte".equals(className))
			return byte.class;
		if ("char".equals(className))
			return char.class;
		if ("short".equals(className))
			return short.class;
		if ("int".equals(className))
			return int.class;
		if ("long".equals(className))
			return long.class;
		if ("float".equals(className))
			return float.class;
		if ("double".equals(className))
			return double.class;
		if ("boolean[]".equals(className))
			return boolean[].class;
		if ("byte[]".equals(className))
			return byte[].class;
		if ("char[]".equals(className))
			return char[].class;
		if ("short[]".equals(className))
			return short[].class;
		if ("int[]".equals(className))
			return int[].class;
		if ("long[]".equals(className))
			return long[].class;
		if ("float[]".equals(className))
			return float[].class;
		if ("double[]".equals(className))
			return double[].class;
		try {
			return arrayForName(className);
		} catch (ClassNotFoundException e) {
			// try to load from java.lang package
			if (className.indexOf('.') == -1) {
				try {
					return arrayForName("java.lang." + className);
				} catch (ClassNotFoundException e2) {
					// ignore, let the original exception be thrown
				}
			}
			throw e;
		}
	}

	private static Class<?> arrayForName(String className) throws ClassNotFoundException {
		return Class.forName(
				className.endsWith("[]") ? "[L" + className.substring(0, className.length() - 2) + ";" : className,
				true, Thread.currentThread().getContextClassLoader());
	}

	public static Class<?> getBoxedClass(Class<?> type) {
		if (type == boolean.class) {
			return Boolean.class;
		} else if (type == char.class) {
			return Character.class;
		} else if (type == byte.class) {
			return Byte.class;
		} else if (type == short.class) {
			return Short.class;
		} else if (type == int.class) {
			return Integer.class;
		} else if (type == long.class) {
			return Long.class;
		} else if (type == float.class) {
			return Float.class;
		} else if (type == double.class) {
			return Double.class;
		} else {
			return type;
		}
	}

	public static Boolean boxed(boolean v) {
		return Boolean.valueOf(v);
	}

	public static Character boxed(char v) {
		return Character.valueOf(v);
	}

	public static Byte boxed(byte v) {
		return Byte.valueOf(v);
	}

	public static Short boxed(short v) {
		return Short.valueOf(v);
	}

	public static Integer boxed(int v) {
		return Integer.valueOf(v);
	}

	public static Long boxed(long v) {
		return Long.valueOf(v);
	}

	public static Float boxed(float v) {
		return Float.valueOf(v);
	}

	public static Double boxed(double v) {
		return Double.valueOf(v);
	}

	public static Object boxed(Object v) {
		return v;
	}

	public static boolean unboxed(Boolean v) {
		return v == null ? false : v.booleanValue();
	}

	public static char unboxed(Character v) {
		return v == null ? '\0' : v.charValue();
	}

	public static byte unboxed(Byte v) {
		return v == null ? 0 : v.byteValue();
	}

	public static short unboxed(Short v) {
		return v == null ? 0 : v.shortValue();
	}

	public static int unboxed(Integer v) {
		return v == null ? 0 : v.intValue();
	}

	public static long unboxed(Long v) {
		return v == null ? 0 : v.longValue();
	}

	public static float unboxed(Float v) {
		return v == null ? 0 : v.floatValue();
	}

	public static double unboxed(Double v) {
		return v == null ? 0 : v.doubleValue();
	}

	public static Object unboxed(Object v) {
		return v;
	}

	public static boolean isNotEmpty(Object object) {
		return getSize(object) > 0;
	}

	public static int getSize(Object object) {
		if (object == null) {
			return 0;
		}
		if (object instanceof Collection<?>) {
			return ((Collection<?>) object).size();
		} else if (object instanceof Map<?, ?>) {
			return ((Map<?, ?>) object).size();
		} else if (object.getClass().isArray()) {
			return Array.getLength(object);
		} else {
			return -1;
		}
	}

	public static URI toURI(String name) {
		try {
			return new URI(name);
		} catch (URISyntaxException e) {
			throw new RuntimeException(e);
		}
	}

	public static Class<?> getGenericClass(Class<?> cls) {
		return getGenericClass(cls, 0);
	}

	public static Class<?> getGenericClass(Class<?> cls, int i) {
		try {
			ParameterizedType parameterizedType = ((ParameterizedType) cls.getGenericInterfaces()[0]);
			Object genericClass = parameterizedType.getActualTypeArguments()[i];
			if (genericClass instanceof ParameterizedType) {
				return (Class<?>) ((ParameterizedType) genericClass).getRawType();
			} else if (genericClass instanceof GenericArrayType) {
				return (Class<?>) ((GenericArrayType) genericClass).getGenericComponentType();
			} else if (genericClass != null) {
				return (Class<?>) genericClass;
			}
		} catch (Throwable e) {
		}
		if (cls.getSuperclass() != null) {
			return getGenericClass(cls.getSuperclass(), i);
		} else {
			throw new IllegalArgumentException(cls.getName() + " generic type undefined!");
		}
	}

	public static boolean isBeforeJava5(String javaVersion) {
		return (javaVersion == null || javaVersion.length() == 0 || "1.0".equals(javaVersion)
				|| "1.1".equals(javaVersion) || "1.2".equals(javaVersion) || "1.3".equals(javaVersion)
				|| "1.4".equals(javaVersion));
	}

	public static boolean isBeforeJava6(String javaVersion) {
		return isBeforeJava5(javaVersion) || "1.5".equals(javaVersion);
	}

	public static String toString(Throwable e) {
		StringWriter w = new StringWriter();
		PrintWriter p = new PrintWriter(w);
		p.print(e.getClass().getName() + ": ");
		if (e.getMessage() != null) {
			p.print(e.getMessage() + "\n");
		}
		p.println();
		try {
			e.printStackTrace(p);
			return w.toString();
		} finally {
			p.close();
		}
	}

	public static void checkBytecode(String name, byte[] bytecode) {
		if (bytecode.length > JIT_LIMIT) {
			System.err
					.println("The template bytecode too long, may be affect the JIT compiler. template class: " + name);
		}
	}

	public static String getSizeMethod(Class<?> cls) {
		try {
			return cls.getMethod("size", new Class<?>[0]).getName() + "()";
		} catch (NoSuchMethodException e) {
			try {
				return cls.getMethod("length", new Class<?>[0]).getName() + "()";
			} catch (NoSuchMethodException e2) {
				try {
					return cls.getMethod("getSize", new Class<?>[0]).getName() + "()";
				} catch (NoSuchMethodException e3) {
					try {
						return cls.getMethod("getLength", new Class<?>[0]).getName() + "()";
					} catch (NoSuchMethodException e4) {
						return null;
					}
				}
			}
		}
	}

	public static String getMethodName(Method method, Class<?>[] parameterClasses, String rightCode) {
		if (method.getParameterTypes().length > parameterClasses.length) {
			Class<?>[] types = method.getParameterTypes();
			StringBuilder buf = new StringBuilder(rightCode);
			for (int i = parameterClasses.length; i < types.length; i++) {
				if (buf.length() > 0) {
					buf.append(",");
				}
				Class<?> type = types[i];
				String def;
				if (type == boolean.class) {
					def = "false";
				} else if (type == char.class) {
					def = "\'\\0\'";
				} else if (type == byte.class || type == short.class || type == int.class || type == long.class
						|| type == float.class || type == double.class) {
					def = "0";
				} else {
					def = "null";
				}
				buf.append(def);
			}
		}
		return method.getName() + "(" + rightCode + ")";
	}

	public static Method searchMethod(Class<?> currentClass, String name, Class<?>[] parameterTypes)
			throws NoSuchMethodException {
		if (currentClass == null) {
			throw new NoSuchMethodException("class == null");
		}
		try {
			return currentClass.getMethod(name, parameterTypes);
		} catch (NoSuchMethodException e) {
			for (Method method : currentClass.getMethods()) {
				if (method.getName().equals(name) && parameterTypes.length == method.getParameterTypes().length
						&& Modifier.isPublic(method.getModifiers())) {
					if (parameterTypes.length > 0) {
						Class<?>[] types = method.getParameterTypes();
						boolean match = true;
						for (int i = 0; i < parameterTypes.length; i++) {
							if (!types[i].isAssignableFrom(parameterTypes[i])) {
								match = false;
								break;
							}
						}
						if (!match) {
							continue;
						}
					}
					return method;
				}
			}
			throw e;
		}
	}

	public static String getInitCode(Class<?> type) {
		if (byte.class.equals(type) || short.class.equals(type) || int.class.equals(type) || long.class.equals(type)
				|| float.class.equals(type) || double.class.equals(type)) {
			return "0";
		} else if (char.class.equals(type)) {
			return "'\\0'";
		} else if (boolean.class.equals(type)) {
			return "false";
		} else {
			return "null";
		}
	}
}